#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <string.h>
#include <sys/time.h>
#include <math.h>
#include <IL/il.h>
#include <omp.h>
#include <pthread.h>

#define GNUPLOT_PATH "/usr/bin/gnuplot"

using namespace std;

int constrain(int x, int min, int max) {
    if(x < min) {
        return min;
    }
    else if(max < x) {
        return max;
    }
    else
        return x;
}


int main(int argc, char **argv) {

	unsigned int image;

	int width, height, bpp, format, line;

	int i = 0, j = 0, x = 0, y = 0;

	int nbThread = 0;

	float time;

	// Initialisation de la bibliothèque
	ilInit();
	ilEnable(IL_ORIGIN_SET);

	// Création d'une image
	ilGenImages(1, &image);

	// Activation de l'image
	ilBindImage(image);

	// Placement des données du fichier lena.png dans l'image courante
	ilLoadImage(argv[1]);

	// Récupération des informations sur l'image : largeur, hauteur, octets par pixel, format
	width = ilGetInteger(IL_IMAGE_WIDTH);
	height = ilGetInteger(IL_IMAGE_HEIGHT); 
	bpp = ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL);
	format = ilGetInteger(IL_IMAGE_FORMAT);

	cout<<"largeur : "<<width<<endl;
	cout<<"hauteur : "<<height<<endl;
	cout<<"bpp : "<<bpp<<endl;

	// Récupération du contenu de l'image (pixels)
	unsigned char* data = ilGetData();

	// Allocation mémoire des images de sortie
	unsigned char* outGreyScale = (unsigned char*)malloc(width * height);
	unsigned char* outSobel = (unsigned char*)malloc(width * height);
	unsigned char* outHistoEq = (unsigned char*)malloc(width * height);
	
	nbThread = atoi(argv[2]);

	time = omp_get_wtime();

	//############### NIVEAU DE GRIS ###############
	
	// boucle pour mettre l'image en niveau de gris
	#pragma omp parallel for num_threads(nbThread) private (i)
	for (i=0; i<width*height*3; i+=3) {
		
		outGreyScale[i/3] = (data[i] * 307 + data[i+1] * 604 + data[i+2] * 113) / 1024;
	}
	
	
	//############### SOBEL ###############
	
	//Sobel Horizontal Mask
	int filtreGx [3][3] = { 

		{1,0,-1},
		{2,0,-2},
		{1,0,-1}
	};
	
		//Sobel Vertical Mask
	int filtreGy [3][3] = { 

		{1,2,1},
		{0,0,0},
		{-1,-2,-1}
	};
	
	int sum = 0;
	
	#pragma omp parallel for num_threads(nbThread) private (y,x,i,j)
	for (y = 0; y < height; y++) {
		
		for (x = 0; x < width; x++) {
			
			int valx = 0;
			int valy = 0;
				
			for(i = -1; i <= 1; i++) {
				
				for(j = -1; j <= 1; j++){
			
					int pixel = y*width+x+i*width+j;
							
					if ((y+i) >= 0 && (y+i) < height && (x+j) >= 0 && (x+j) < width){
					
						valx += outGreyScale[pixel] * filtreGx[ i + 1][ j + 1];
						valy += outGreyScale[pixel] * filtreGy[ i + 1][ j + 1];
						
					}
					
				} // fin for j

			} // fin for i
		
			//------ recalcule des pixels ----
			
			//sum = abs(valx) + abs(valy);
			sum = sqrt(valx*valx + valy*valy);
		
			outSobel[y*width+x] = constrain(sum,0,255);

		} // fin for x
	} // fin for y
	
	
	//############### HISTOGRAMME ###############

    int histo [256];
    double histoNorm [256];
    double histoCumule [256];
	double somme = 0.;

	// initialisation du tableau de l'histogramme
	#pragma omp parallel for num_threads(nbThread) private (i)
	for (i=0; i<256; i++) {
	
		histo[i] = 0;
	}

	// remplissage du tableau de l'histogramme
	#pragma omp parallel for num_threads(nbThread) private (i)
	for (i=0; i<width*height; i++) {
	
		histo[outGreyScale[i]] ++;
	}

	//calcul de l'histogramme normalisé
  	/*for (int i=0; i<256; i++){

		histoNorm[i] = (float)histo[i] / (float)(width*height);
		//cout<<i<<" : "<<histoNorm[i]<<endl;
    }*/


	// calcul de l'histogramme cumulé
	#pragma omp parallel for num_threads(nbThread) private (i)
	for (i=0; i<256; i++) {
		
		somme += histo[i];
		histoCumule[i] = somme;

	}

 	//Egalisation de l'histogramme
 	#pragma omp parallel for num_threads(nbThread) private (i)
    for(i=0;i< width*height;i++){

		outHistoEq[i] = (256.0  / (double)(width*height)) * histoCumule[outGreyScale[i]]-1;
    }

    // temps d'exécution
	time = omp_get_wtime() - time;
	printf("Temps d'exécution : %3f \n", time);
    

    /*int Transfo [256];
	int N = 32;
	int Nr = 4;
	float moyenne = (width*height) / N;
	float bande= Nr/N;
	float centre = bande/2;
	int idebut = 0;
	int ifin = 0;
	int cumul = 0;
	
	while(ifin < Nr) {
		cumul = 0;
		while((cumul < moyenne) && (ifin<Nr)) {
			cumul = cumul + histo[ifin];
			ifin ++;
		}
		for (int i=idebut; i < ifin; i++) {
			Transfo[i] = centre;
		}
		idebut = ifin ;
		centre = centre + bande;
	}
	for(int i=0;i< width*height;i++) {
		outHistoEq[i] = Transfo[outGreyScale[i]];
	}*/

	//############################ GNUPLOT ################################

	if (argc > 3) {

		if(strcmp(argv[3],"graph") ==0 ) {

			// Pour l'histogramme de l'image en niveau de gris :

			// On ouvre le fichier data.dat (ouverture en écriture avec effacement du fichier ouvert)
			ofstream fichier("gnuplot/dataHisto.dat", ios::out | ios::trunc);
		    if(fichier)
		    {
		    	// On écrit les valeurs de l'histogramme dans le fichier
		    	for (int i=0; i<width*height; i++) {
		    		fichier << (int)outGreyScale[i] << endl;
		    	}

				fichier.close();
		    }
		    else
		    	cerr << "Impossible d'ouvrir le fichier !" << endl;
			
			cout<<"FIN ecriture fichier histo"<<endl;

			// Création du graph avec un script gnuplot à partir du fichier data.dat dans lequel on a écrit l'histogramme juste avant
			FILE *gp;
		 
		    gp = popen(GNUPLOT_PATH, "w");
		    if(gp == NULL){
		        fprintf(stderr, "Oops, I can't find %s.", GNUPLOT_PATH);
		         exit(EXIT_FAILURE);
		    }
		    fprintf(gp, "cd 'gnuplot'\n load \"configHisto\"\n");
		    fflush(gp); /* On oublie pas le buffer. */
		    //getchar();
		    pclose(gp);

		    cout<<"FIN ecriture graph histo"<<endl;


			// Pour l'histogramme avec égalisation :


		    // On ouvre le fichier data.dat (ouverture en écriture avec effacement du fichier ouvert)
			ofstream fichier2("gnuplot/dataHistoEq.dat", ios::out | ios::trunc);
		    if(fichier2)
		    {
		    	// On écrit les valeurs de l'histogramme dans le fichier
		    	for (int i=0; i<width*height; i++) {
		    		fichier2 << (int)outHistoEq[i] << endl;
		    	}

				fichier2.close();
		    }
		    else
		    	cerr << "Impossible d'ouvrir le fichier !" << endl;
			
			cout<<"FIN ecriture fichier histo égalisé"<<endl;

			// Création du graph avec un script gnuplot à partir du fichier data.dat dans lequel on a écrit l'histogramme juste avant
			FILE *gp2;
		 
		    gp2 = popen(GNUPLOT_PATH, "w");
		    if(gp2 == NULL){
		        fprintf(stderr, "Oops, I can't find %s.", GNUPLOT_PATH);
		         exit(EXIT_FAILURE);
		    }
		    fprintf(gp2, "cd 'gnuplot'\n load \"configHistoEq\"\n");
		    fflush(gp2); /* On oublie pas le buffer. */
		    //getchar();
		    pclose(gp2);

		    cout<<"FIN ecriture graph histo égalisé"<<endl;

		}
	}

	
	//############### ECRITURE IMAGES ###############
	
	//convertion de l'image en valeur luminence
	//ilConvertImage(IL_LUMINANCE, IL_UNSIGNED_BYTE);
	ilTexImage(width, height,0,1,IL_LUMINANCE, IL_UNSIGNED_BYTE,outGreyScale);
	
	// Le contenu de l'image de sortie est dans le tableau out
	ilSetData(outGreyScale);

	// Si le fichier existe déjà , écrase son contenu
	ilEnable(IL_FILE_OVERWRITE);

	// Sauvegarde de l'image
	ilSaveImage("outGreyScale.png");

	
	// Image avec Sobel
	ilTexImage(width, height,0,1,IL_LUMINANCE, IL_UNSIGNED_BYTE,outSobel);
	ilSetData(outSobel);
	ilEnable(IL_FILE_OVERWRITE);
	ilSaveImage("outSobel.png");
	
	// Image avec égalisation de l'histogramme
	ilTexImage(width, height,0,1,IL_LUMINANCE, IL_UNSIGNED_BYTE,outHistoEq);
	ilSetData(outHistoEq);
	ilEnable(IL_FILE_OVERWRITE);
	ilSaveImage("outHistoEq.png");

	ilDeleteImages(1, &image); 

	free(outGreyScale);
	free(outSobel);
	free(outHistoEq);

	return 0;

}

// g++ chargementImage.cpp -lIL -O3 -o seq
// g++ chargementImage.cpp -fopenmp -lIL -O3 -o seq